#include "shapemanipulators.h"
#include "collisionshapes.h"
#include "collisionshapesdocument.h"
#include "../application.h"
#include <QPainter>

ShapeManipulatorControlPoint::ShapeManipulatorControlPoint(int nIndex)
    : m_nIndex(nIndex)
    , m_constraint(NO_CONSTRAINT)
{

}

void ShapeManipulatorControlPoint::setPosition(const QPointF &pos, bool useConstraint)
{
    if(!useConstraint || m_constraint == NO_CONSTRAINT)
    {
        m_pos = pos;
    }
    else if(m_constraint == X_AXIS)
    {
        m_pos.setX(pos.x());
    }
    else if(m_constraint == Y_AXIS)
    {
        m_pos.setY(pos.y());
    }
}

QPainterPath ShapeManipulatorControlPoint::shape(float manipulatorSize) const
{
    QPainterPath mShape;

    if(m_nIndex == -1)
    {
        mShape.addEllipse(m_pos, manipulatorSize, manipulatorSize);
    }
    else
    {
        mShape.addRect(m_pos.x() - manipulatorSize, m_pos.y() - manipulatorSize,
                       manipulatorSize*2, manipulatorSize*2);
    }

    return mShape;
}

void ShapeManipulatorControlPoint::setConstraint(ShapeManipulatorControlPoint::MovingConstraint c)
{
    m_constraint = c;
}

// ShapeManipulator Bas Class

ShapeManipulator::ShapeManipulator()
    : m_pGrabbedPoint(0)
{
}

bool ShapeManipulator::mousePress(const QPointF& transformedMousePos)
{
    // return false if no point of interest is selected

    QPointer<CollisionShapesDocument> doc = Application::app().collisionShapesDocument();
    Q_ASSERT(doc);

    if(m_translationPoint.shape(doc->manipulatorSize()).contains(transformedMousePos))
    {
        m_pGrabbedPoint = &m_translationPoint;
    }
    else
    {
        foreach(ShapeManipulatorControlPoint* cp, m_controlPoints)
        {
            if(cp->shape(doc->manipulatorSize()).contains(transformedMousePos))
            {
                m_pGrabbedPoint = cp;
                break;
            }
        }
    }

    return m_pGrabbedPoint != 0;
}

void ShapeManipulator::mouseMove(const QPointF &transformedMousePos)
{
    if(m_pGrabbedPoint)
    {
        m_pGrabbedPoint->setPosition(transformedMousePos, true);
        updateShape();
    }
}

void ShapeManipulator::mouseRelease(const QPointF &transformedMousePos)
{
    m_pGrabbedPoint = 0;
}

void ShapeManipulator::draw(QPainter &painter) const
{
    drawManipulator(painter, m_translationPoint);
    foreach(ShapeManipulatorControlPoint* cp, m_controlPoints)
    {
        drawManipulator(painter, *cp);
    }
}

void ShapeManipulator::updateControlPoints(const QPointF &offset)
{
    foreach(ShapeManipulatorControlPoint* cp, m_controlPoints)
    {
        QPointF pos = cp->position();
        pos += offset;
        cp->setPosition(pos);
    }
}

void ShapeManipulator::drawManipulator(QPainter &painter, const ShapeManipulatorControlPoint &cp) const
{
    QPen pen(Qt::red, 1, Qt::SolidLine);
    painter.setPen(pen);

    QPointer<CollisionShapesDocument> doc = Application::app().collisionShapesDocument();
    Q_ASSERT(doc);

    painter.drawPath(cp.shape(doc->manipulatorSize()));
}

void ShapeManipulator::setShape(QPointer<CollisionShape> shape)
{
    m_shape = shape;
    setupManipulator();
    m_pGrabbedPoint = 0;
}

ShapeManipulatorEngine::ShapeManipulatorEngine()
{
    // init manipulators
    m_circleManipulator = new CircleShapeManipulator;
    m_rectManipulator = new RectShapeManipulator;
    m_polyManipulator = new PolyShapeManipulator;
}

void ShapeManipulatorEngine::setShape(QPointer<CollisionShape> shape)
{
    if(!shape)
    {
        m_manipulator = 0;
        return;
    }
    if(QPointer<CollisionCircle> circle = qobject_cast<CollisionCircle*>(shape))
    {
        m_manipulator = m_circleManipulator;
    }
    else if(QPointer<CollisionBox> rect = qobject_cast<CollisionBox*>(shape))
    {
        m_manipulator = m_rectManipulator;
    }
    else if(QPointer<CollisionPoly> poly = qobject_cast<CollisionPoly*>(shape))
    {
        m_manipulator = m_polyManipulator;
    }

    Q_ASSERT(m_manipulator);
    m_manipulator->setShape(shape);
}

void ShapeManipulatorEngine::drawManipulator(QPainter &painter)
{
    Q_ASSERT(m_manipulator);
    m_manipulator->draw(painter);
}

bool ShapeManipulatorEngine::mousePress(const QPointF &transformedPoint)
{
    Q_ASSERT(m_manipulator);
    return m_manipulator->mousePress(transformedPoint);
}

void ShapeManipulatorEngine::mouseMove(const QPointF &transformedPoint)
{
    Q_ASSERT(m_manipulator);
    m_manipulator->mouseMove(transformedPoint);
}

void ShapeManipulatorEngine::mouseRelease(const QPointF &transformedPoint)
{
    Q_ASSERT(m_manipulator);
    m_manipulator->mouseRelease(transformedPoint);
}

bool ShapeManipulatorEngine::manipulationMode() const
{
    return m_manipulator != 0;
}

// Circle Shape Manipulator

CircleShapeManipulator::CircleShapeManipulator()
{
}

void CircleShapeManipulator::setupManipulator()
{
    Q_ASSERT(m_shape);
    QPointer<CollisionCircle> circle = qobject_cast<CollisionCircle*>(m_shape);

    QPointF center = circle->center();
    float radius = circle->radius();

    qDeleteAll(m_controlPoints);
    m_controlPoints.clear();
    m_translationPoint.setPosition(center);

    ShapeManipulatorControlPoint* radiusManipulator = new ShapeManipulatorControlPoint(0);
    radiusManipulator->setPosition(QPointF(center.x() + radius, center.y()));
    radiusManipulator->setConstraint(ShapeManipulatorControlPoint::X_AXIS);
    m_controlPoints.append(radiusManipulator);
}

void CircleShapeManipulator::updateShape()
{
    Q_ASSERT(m_pGrabbedPoint);
    Q_ASSERT(m_shape);
    QPointer<CollisionCircle> circle = qobject_cast<CollisionCircle*>(m_shape);

    if(m_pGrabbedPoint->index() == -1)
    {
        QPointF offset = m_translationPoint.position() - circle->center();

        circle->setCenter(m_translationPoint.position().x(),
                          m_translationPoint.position().y());

        updateControlPoints(offset);
    }
    else
    {
        QPointF center = circle->center();
        float radius = m_pGrabbedPoint->position().x() - center.x();
        circle->setRadius(radius);
    }
}

// Rect Shape Manipulator

RectShapeManipulator::RectShapeManipulator()
{
}

void RectShapeManipulator::setupManipulator()
{
    Q_ASSERT(m_shape);
    QPointer<CollisionBox> rectangle = qobject_cast<CollisionBox*>(m_shape);

    QRectF rect = rectangle->rect();

    qDeleteAll(m_controlPoints);
    m_controlPoints.clear();
    m_translationPoint.setPosition(rect.center());

    ShapeManipulatorControlPoint* widthManipulator = new ShapeManipulatorControlPoint(0);
    ShapeManipulatorControlPoint* heightManipulator = new ShapeManipulatorControlPoint(1);
    ShapeManipulatorControlPoint* sizeManipulator = new ShapeManipulatorControlPoint(2);

    widthManipulator->setConstraint(ShapeManipulatorControlPoint::X_AXIS);
    widthManipulator->setPosition(QPointF(rect.right(), rect.center().y()));

    heightManipulator->setConstraint(ShapeManipulatorControlPoint::Y_AXIS);
    heightManipulator->setPosition(QPointF(rect.center().x(), rect.bottom()));

    sizeManipulator->setPosition(rect.bottomRight());

    m_controlPoints.append(widthManipulator);
    m_controlPoints.append(heightManipulator);
    m_controlPoints.append(sizeManipulator);
}

void RectShapeManipulator::updateShape()
{
    Q_ASSERT(m_pGrabbedPoint);
    Q_ASSERT(m_shape);
    QPointer<CollisionBox> rectangle = qobject_cast<CollisionBox*>(m_shape);

    QRectF shapeRect = rectangle->boxRect();

    if(m_pGrabbedPoint->index() == -1)
    {
        QPointF offset = m_translationPoint.position() - shapeRect.center();

        shapeRect.moveCenter(m_translationPoint.position());
        rectangle->setBoxRect(shapeRect);

        updateControlPoints(offset);
    }
    // width manipulator
    else if(m_pGrabbedPoint->index() == 0)
    {
        float offset = m_pGrabbedPoint->position().x() - shapeRect.right();
        shapeRect.adjust(-offset, 0, offset, 0);
        rectangle->setBoxRect(shapeRect);

        // update size manipulator
        ShapeManipulatorControlPoint* sizeManipulator = m_controlPoints[2];
        sizeManipulator->setPosition(QPointF(m_pGrabbedPoint->position().x(),
                             sizeManipulator->position().y()));
    }
    // height manipulator
    else if(m_pGrabbedPoint->index() == 1)
    {
        float offset = m_pGrabbedPoint->position().y() - shapeRect.bottom();
        shapeRect.adjust(0, -offset, 0, offset);
        rectangle->setBoxRect(shapeRect);

        // update size manipulator
        ShapeManipulatorControlPoint* sizeManipulator = m_controlPoints[2];
        sizeManipulator->setPosition(QPointF(sizeManipulator->position().x(),
                             m_pGrabbedPoint->position().y()));
    }
    // size manipulator
    else if(m_pGrabbedPoint->index() == 2)
    {
        QPointF offset = m_pGrabbedPoint->position() - shapeRect.bottomRight();
        shapeRect.adjust(0, 0, offset.x(), offset.y());
        rectangle->setBoxRect(shapeRect);

        m_translationPoint.setPosition(shapeRect.center());

        // update width and height manipulators
        ShapeManipulatorControlPoint* widthManipulator = m_controlPoints[0];
        widthManipulator->setPosition(QPointF(m_pGrabbedPoint->position().x(),
                             shapeRect.center().y()));

        ShapeManipulatorControlPoint* heightManipulator = m_controlPoints[1];
        heightManipulator->setPosition(QPointF(shapeRect.center().x(),
                             m_pGrabbedPoint->position().y()));
    }
}


PolyShapeManipulator::PolyShapeManipulator()
{
}

void PolyShapeManipulator::setupManipulator()
{
    Q_ASSERT(m_shape);
    QPointer<CollisionPoly> poly = qobject_cast<CollisionPoly*>(m_shape);

    QPolygonF polygon = poly->poly();

    qDeleteAll(m_controlPoints);
    m_controlPoints.clear();

    QPointF center;
    foreach(QPointF pt, polygon)
    {
        center += pt;
    }
    center /= (float)polygon.size();

    m_translationPoint.setPosition(center);

    int index = 0;
    foreach(QPointF pt, polygon)
    {
        ShapeManipulatorControlPoint* vertexManipulator = new ShapeManipulatorControlPoint(index++);
        vertexManipulator->setPosition(pt);
        m_controlPoints.append(vertexManipulator);
    }
}

void PolyShapeManipulator::updateShape()
{
    Q_ASSERT(m_pGrabbedPoint);
    Q_ASSERT(m_shape);
    QPointer<CollisionPoly> poly = qobject_cast<CollisionPoly*>(m_shape);

    QPolygonF polygon = poly->poly();

    if(m_pGrabbedPoint->index() == -1)
    {
        QPointF center;
        foreach(QPointF pt, polygon)
        {
            center += pt;
        }
        center /= (float)polygon.size();

        QPointF offset = m_translationPoint.position() - center;

        updateControlPoints(offset);
    }
    else
    {
        QPointF pos = m_pGrabbedPoint->position();
        polygon[m_pGrabbedPoint->index()] = pos;
        poly->setPoly(polygon);

        QPointF center;
        foreach(QPointF pt, polygon)
        {
            center += pt;
        }
        center /= (float)polygon.size();

        m_translationPoint.setPosition(center);
    }
}
